.. _глава_30_start:

Оператор switch и case
=======================

.. index::
    switch
    case
    default

switch - это оператор, который позволяет сравнивать значение выражения с
несколькими значениями. Он похож, но не совпадает с цепочкой "if, else if, else".
оператор case используется для указания значений, которые должны сравниваться с
выражением switch. case является частью оператора switch, а не самим оператором.
switch принимает выражение в круглых скобках, сравнивает значение этого
выражения со значениями case и выполняет операции в случае совпадения значения
указанного в case и значения указанного в switch. Его синтаксис состоит из блока переключения, который
содержит один или несколько разделов обращения и раздел по умолчанию:

.. code-block:: d

    switch (expression)
    {
    case value_1 : // операции будут выполнены если значение expression равно value_1
        // ...
        break;
    case value_2 : // операции будут выполнены если значение expression равно value_2
        // ...
        break;
        // ... прочие случаи ...
    default : // операции будут выполнены если значение expression не совпало ни с одним из значений указанных в case
        // ...
        break;
    }

Выражение, которое принимает switch, не используется непосредственно как логическое выражение. Оно
не вычисляется как "если это условие истинно", как это было бы в операторе if.
Значение выражения switch используется при сравнении равенства со значениями case.
Это похоже на цепочку "if, else if, else", в которой есть только сравнения на равенство:

.. code-block:: d

    auto value = expression;
    if (value == value_1)
    {
        // операции будут выполнены если значение expression равно value_1
        // ...
    }
    else if (value == value_2)
    {
        // операции будут выполнены если значение expression равно value_1
        // ...
    }
    // ... прочие случаи ...
    }
    else
    {
        // операции для прочих случаев
        // ...
    }


Однако приведенные выше "if, else if, else" не являются точным эквивалентом
инструкции switch. Причины этого будут объяснены в следующих разделах.
Если значение case совпадает со значением выражения switch, то выполняются операции,
которые находятся в данном блоке. Если ни одно значение не совпадает, то выполняются операции,
которые заданы по умолчанию.

.. index::
    goto

Оператор goto
-------------

В большинстве языков программирования обычно не рекомендуется использовать goto.
Однако goto полезен в операторах switch в некоторых ситуациях, не будучи
таким же проблемным, как и при других видах использования. Оператор goto будет рассмотрен более подробно
в следующей главе (:ref:`Метки и goto<Метки и goto>`).
case не вводит область видимости, как это делает оператор if. Как только операции
в области if или else завершены, вычисление всего оператора if
также завершается. Этого не происходит с разделами case; как только найден соответствующий
case, выполнение программы переходит к этому case и выполняет
операции в соответствии с ним. Когда это необходимо в редких ситуациях, goto case заставляет выполнение
программы перейти к следующему case:


.. code-block:: d

    switch (value)
    {
    case 5 : writeln("five");
        goto case; // переходим к следующему случаю
    case 4 : writeln("four");
        break;
    default : writeln("unknown");
        break;
    }

Если значение равно 5, выполнение продолжается в строке case 5, и программа
выводит "пять". Затем оператор goto case приводит к продолжению выполнения до
следующего case, и в результате также выводится "four".:

.. code-block:: console

    five
    four

goto может отображаться тремя способами в разделах case:

#. goto case приводит к продолжению выполнения (переходу) к следующему case.
#. goto default приводит к продолжению выполнения в разделе по умолчанию.
#. выражение goto case expression приводит к продолжению выполнения в том случае, который
   соответствует этому выражению.

Следующая программа демонстрирует эти три варианта использования, используя преимущества цикла
foreach:

.. code-block:: d

    import std.stdio;

    void main()
    {
        foreach (value; [1, 2, 3, 10, 20])
        {
            writefln("--- value: %s ---", value);
            switch (value)
            {
            case 1:
                writeln("case 1");
                goto case;
            case 2:
                writeln("case 2");
                goto case 10;
            case 3:
                writeln("case 3");
                goto default;
            case 10:
                writeln("case 10");
                break;
            default:
                writeln("default");
                break;
            }
        }
    }


Вывод

.. code-block:: console

    case 1
    case 2
    case 10
    value: 2 ---
    case 2
    case 10
    value: 3 ---
    case 3
    default
    value: 10 ---
    case 10
    value: 20 ---
    default

Выражение оператора должно быть целочисленным, строковым или типа bool
----------------------------------------------------------------------

При сравнении на равенство в операторах if может использоваться любой тип. С другой
стороны, тип выражения switch ограничен всеми целочисленными типами, всеми строковыми
типами и bool.

.. code-block:: d

    string op =  /* ... */ ;
    // ...
    switch (op)
    {
    case "add" : result = first + second;
        break;
    case "subtract" : result = first - second;
        break;
    case "multiply" : result = first * second;
        break;
    case "divide" : result = first / second;
        break;
    default : throw new Exception(format("Unknown operation: %s", op));
    }


*Примечание: Приведенный выше код генерирует исключение, когда операция не распознается
программой.* Мы рассмотрим исключения в следующей главе (стр. 192).
Хотя можно также использовать выражения bool, поскольку bool имеет только
два значения, может быть более подходящим использовать оператор if или троичный оператор
(?:) с этим типом.

Диапазоны значений
------------------

Диапазоны значений могут быть указаны с помощью .. между предложениями case:

.. code-block:: d

    switch (dieValue)
    {
    case 1 : writeln("You won");
        break;
    case 2: .. case 5 : writeln("It's a draw");
        break;
    case 6 : writeln("I won");
        break;
    default : /* Программа никогда не должна попасть сюда, потому что приведенные выше случаи
        * охватывают весь диапазон допустимых значений матрицы.
        * (Смотрите " final switch" ниже.) */
        break;
    }

Приведенный выше код определяет, что игра заканчивается вничью, когда значение кубика равно 2,
3, 4 или 5.

Различные значения
------------------

Давайте предположим, что это ничья для значений 2 и 4, а не для значений, которые
находятся в диапазоне [2, 5]. Различные значения регистра разделяются запятыми:

.. code-block:: d

    case 2, 4:
        writeln("It's a draw");
        break;

Оператор final switch
---------------------

Оператор final switch работает аналогично обычному оператору switch,
со следующими отличиями:

* У него не может быть раздела по умолчанию. Обратите внимание, что этот раздел не имеет смысла, когда
  разделы case в любом случае охватывают весь диапазон значений, как это было с
  шестью значениями матрицы выше.
* Диапазоны значений нельзя использовать с case (могут быть разные значения).
* Если выражение относится к типу enum, все значения этого типа должны быть
  охвачены предложениями case (мы рассмотрим типы enum в следующей главе).

Когда использовать опрератор switch
--------------------------------------

switch подходит для сравнения значения выражения с набором
значений, которые известны во время компиляции.
Когда есть только два значения для сравнения, оператор if может иметь больше
смысла. Например, чтобы проверить, орел это или решка:

.. code-block:: d

    if (headsTailsResult == heads)
    {
    // ...
    } else {
    // ...
    }

Как правило, switch больше подходит, когда есть три или более значений для
сравнения.
Когда необходимо обработать все значения, предпочтите final switch. Это
особенно относится к типам enum.

Упражнения
----------

#. Напишите программу-калькулятор, поддерживающую арифметические операции. Попросите
   программу сначала прочитать операцию в виде строки, затем два значения типа double
   из входных данных. Калькулятор должен напечатать результат операции.
   Например, когда операция и значения равны "add" и "5 7" соответственно,
   программа должна вывести 12.
   Ввод может быть прочитан как в следующем коде:

.. code-block:: d

     string op;
    double first;
    double second;
    // ...
    op = strip(readln());
    readf(" %s %s", &first, &second);

#. Улучшите калькулятор, чтобы он поддерживал операторы типа "+" в дополнение к словам типа
   "добавить".

#. Попросите программу генерировать исключение для неизвестных операторов. Мы рассмотрим
   исключения в следующей главе (стр. 192). На данный момент адаптируйте оператор throw,
   использованный выше, к вашей программе.
